<?php
	
	namespace Aspic\Route;
	use Aspic\Security\Security as S;
	
	class PathRoute extends AbstractRoute {
		const DEFAULT_PARAM_REQUIREMENT = '[^\/]+';
		
		// :paramName:
		const PARAM_REGEX = '#\\:(?P<param>[a-z0-9_-]+)\\:#i';
		const ESCAPED_PARAM_REGEX = '#\\\\:(?P<param>[a-z0-9_-]+)\\\\:#i';
		
		// (brackets []), optionnal part of the path like in 'users/list[/orderBy/:orderBy:][/type/:type:]
		// There must be a param (:param:) into the [] (to be logic because route will be reverse differently if the bracket param is or not set)
		const OPTIONNAL_PARAM_REGEX = '#\\[([^\\]]+)\\]#';
		const ESCAPED_OPTIONNAL_PARAM_REGEX = '#\\\\\\[([^\\]]+)\\\\\\]#';
		
		// ESCAPE REGEX are used to create a regex from "pseudo regex" given, this is not totaly an escaped version of the other constant
		// We search in an espace string (see preg_quote usage) so we doubled some slashs
		
		protected $_pattern;
		protected $_params; // All the params of the route
		protected $_regexSepChar;
		
		/**
		* @param array $requirements For each param (array key), its requirements (regex)
		 * ex: array('name' => '\w')
		*/
		public function __construct($pattern, $to, array $requirements = array()) {
			preg_match_all(static::PARAM_REGEX, $pattern, $params);
			
			$this->_pattern = $pattern;
			$this->_to = $to;
			$this->_params = $params['param'];
			
			$regex = $pattern;
			$regex = preg_quote($regex, '#');
			
			// Building optionnal part
			$regex = preg_replace(static::ESCAPED_OPTIONNAL_PARAM_REGEX, '(?:$1)?', $regex);
			
			$defaultParamValue = static::DEFAULT_PARAM_REQUIREMENT;
			
			// Building param part (with requirements)
			$regex = preg_replace_callback(static::ESCAPED_PARAM_REGEX, function($matches) use ($requirements, $defaultParamValue) {
				$paramName = $matches[1];
				
				if (isset($requirements[$paramName])) {
					$paramValue = $requirements[$paramName];
				}
				else {
					$paramValue = $defaultParamValue;
				}
				
				$paramRegex = '(?P<'.$paramName.'>'.$paramValue.')';
				
				return $paramRegex;
				
			}, $regex);
			
			$regex = '#\\A'.$regex.'\\z#';
			
			$this->_fromRegex = $regex;
			
			return $this;
		}
		
		public function match($src) {
			return (bool)preg_match($this->_fromRegex, $src);
		}
		
		public function parse($src) {
			$actionPath = $this->_to;
			
			preg_match($this->_fromRegex, $src, $params);
			
			foreach($params as $k => $v) {
				$actionPath = preg_replace('#:'.preg_quote($k, '#').':#', $v, $actionPath);
			}
			
			$regexParams = array();
			
			foreach($params as $k => $v) {
				if(!is_numeric($k) AND !empty($v)) { // !empty to avoid returning get '' when an optionnal ( ) is not set
					$regexParams[$k] = $v;
				}
			}
			
			$actionData = array();
			
//			$actionData = static::parseActionPath($actionPath);
			$actionData['data'] = $actionPath;
			$actionData['params'] = array_flip($this->_params);
			$actionData['params'] = array_fill_keys($this->_params, null);
			$actionData['params'] = array_merge($actionData['params'], $regexParams);
			
			if(!$actionData) {
				$this->throwActionDataError();
			}
			else {	
				return $actionData;
			}
		}
		
		public function reverseRoute(array $params = array(), $encodeParams = true) {
			$reversedRoute = $this->_pattern;
			
			// Replace all :param: by it's value if it exists in $params (developper has set it)
			$reversedRoute = preg_replace_callback(static::PARAM_REGEX, function ($matches) use($params, $encodeParams) {
				$param = $matches['param'];
				// var_dump($matches);
				if(isset($params[$param])) {
					if($encodeParams) {
						return urlencode($params[$param]);
					}
					else {	
						return $params[$param];
					}
				}
				else {
					return $matches[0];
				}
			}, $reversedRoute);
			// var_dump($reversedRoute);
			$paramRegex = static::PARAM_REGEX;
			
			// Looking for optionnal part of the path
			$reversedRoute = preg_replace_callback(static::OPTIONNAL_PARAM_REGEX, function ($matches) use($paramRegex) {
				$optPart = $matches[1];
				
				// If the optionnal part contents an unset param, we remove the optionnal part
				if(preg_match($paramRegex, $optPart)) {
					return '';
				}
				// Else we remove brackets [] from the result path
				else {
					return $optPart;
				}
				
			}, $reversedRoute);
			
			// We check if there are still unset params and throw an error in that case
			$nbStillParams = preg_match_all(static::PARAM_REGEX, $reversedRoute, $matches);
			
			if($nbStillParams > 0) {
				$stillParams = $matches[1];
				$stillParamsStr = implode(', ', $stillParams);
				
				throw new \Exception('Param(s) "'.S::escapeXss($stillParamsStr).'" not found in params array');
			}
			
			return $reversedRoute;
		}
		
	}
	
?>